#include "Client.h"

ThreadCtrl::ThreadCtrl(Client* cli) : ClientThread(cli, "ThreadCtrl") {
	client = cli;
	ticks = geGetTick();
	ticksLastForward = 0;
	ticksLastJump = 0;
	t = ((float)geGetTick()) / 1000.0;
	dt = 0.0;
	running = false;
	speed = 1.0;
}

void ThreadCtrl::run(){
	Player* player = client->getPlayer();
	Location loc = player->getLocation();
	Location new_loc = Location(loc);

	static float jump_force = 0.0;
	bool moved = false;

	geReadKeys(client->keys);
	
	if(client->keys->pressed[GEK_ESCAPE]){
		geCursorRoundMode(false);
		geCursorVisible(true);
		while(1){
			geReadKeys(client->keys);
			if(geKeysToggled(client->keys, GEK_LBUTTON)){
				geCursorRoundMode(true);
				geCursorVisible(false);
				break;
			}
		}
		geReadKeys(client->keys);
	}

	if(geKeysToggled(client->keys, 'G')){
		client->options.debug = !client->options.debug;
	}

	if(geKeysToggled(client->keys, 'F')){
		player->setFlying(!player->isFlying());
	}
	if(geKeysToggled(client->keys, 'C')){
	//	collide = !collide;
	}

	if(geKeysToggled(client->keys, 'Z')){
		if(geGetTick() - ticksLastForward < 400){
			speed = 0.6;
			running = true;
		}
		ticksLastForward = geGetTick();
	}
	if(geKeysToggled(client->keys, GEK_SPACE)){
		if(geGetTick() - ticksLastJump < 400){
			player->setFlying(!player->isFlying());
		}
		ticksLastJump = geGetTick();
	}
	if(player->isFlying()){
		player->SetBrakeFactor(1.05, 1.05, 1.15);
	}else{
		player->SetBrakeFactor(1.25, 1.25, 1.25);
	}
	
	if(geKeysToggled(client->keys, 'J')){
		client->getThreadRender()->ssao = (client->getThreadRender()->ssao + 1) % 3;
	}
	if(client->keys->pressed['Z']){
		player->setMoved(true);
		moved = true;
		player->ApplyForce(2800.0 * cosf(client->cam->RotH_rad) / speed, 2800.0 * sinf(client->cam->RotH_rad) / speed, 0.0);
	}
	if(client->keys->pressed['S']){
		player->setMoved(true);
		moved = true;
		player->ApplyForce(2800.0 * -cosf(client->cam->RotH_rad) / speed, 2800.0 * -sinf(client->cam->RotH_rad) / speed, 0.0);
	}
	if(client->keys->pressed['Q']){
		player->setMoved(true);
		moved = true;
		player->ApplyForce(2800.0 * -cosf(client->cam->RotH_rad - 1.57079632) / speed, 2800.0 * -sinf(client->cam->RotH_rad - 1.57079632) / speed, 0.0);
	}
	if(client->keys->pressed['D']){
		player->setMoved(true);
		moved = true;
		player->ApplyForce(2800.0 * cosf(client->cam->RotH_rad - 1.57079632) / speed, 2800.0 * sinf(client->cam->RotH_rad - 1.57079632) / speed, 0.0);
	}
	if(client->keys->pressed[GEK_SPACE]){
		player->setMoved(true);
		//moved = true;
		if(player->isFlying()){
			player->ApplyForce(0.0, 0.0, 5000.0 / speed);
		}else
		if(player->isOnGround()){
			jump_force = 8000.0;
		}
	}
	if(client->keys->pressed[GEK_SHIFT]){
		if(player->isFlying()){
			player->ApplyForce(0.0, 0.0, -5000.0 / speed);
		}else{
			speed = 2.0;
		}
	}else if(speed > 1.0){
		speed = 1.0;
	}
	if(client->keys->pressed[GEK_CONTROL]){
		speed = 0.05;
	}
	if(!moved){
		speed = 1.0;
	}
	
	if(player->hasMoved()){
		ge_Vector3d* vec = player->getForces();
		Block* n = client->getWorld()->FindBlock(floor(loc.getX() - 0.35), floor(loc.getY()), round(loc.getZ()));
		Block* s = client->getWorld()->FindBlock(floor(loc.getX() + 0.35), floor(loc.getY()), round(loc.getZ()));
		Block* e = client->getWorld()->FindBlock(floor(loc.getX()), floor(loc.getY() - 0.35), round(loc.getZ()));
		Block* w = client->getWorld()->FindBlock(floor(loc.getX()), floor(loc.getY() + 0.35), round(loc.getZ()));
		Block* n2 = client->getWorld()->FindBlock(floor(loc.getX() - 0.35), floor(loc.getY()), round(loc.getZ()) + 1.0);
		Block* s2 = client->getWorld()->FindBlock(floor(loc.getX() + 0.35), floor(loc.getY()), round(loc.getZ()) + 1.0);
		Block* e2 = client->getWorld()->FindBlock(floor(loc.getX()), floor(loc.getY() - 0.35), round(loc.getZ()) + 1.0);
		Block* w2 = client->getWorld()->FindBlock(floor(loc.getX()), floor(loc.getY() + 0.35), round(loc.getZ()) + 1.0);
		Block* top = client->getWorld()->FindBlock(floor(loc.getX()), floor(loc.getY()), round(loc.getZ()) + 2.0);
		if(vec->x < 0.0 && ((n && (n->getMaterial() && n->getMaterial()->isCollidable())) || (n2 && (n2->getMaterial() && n2->getMaterial()->isCollidable())))){
			player->ResetForces(true, false, false);
			player->getPhysicState()->momentum.x = 0.0;
			speed = 1.0;
		}
		if(vec->x > 0.0 && ((s && (s->getMaterial() && s->getMaterial()->isCollidable())) || (s2 && (s2->getMaterial() && s2->getMaterial()->isCollidable())))){
			player->ResetForces(true, false, false);
			player->getPhysicState()->momentum.x = 0.0;
			speed = 1.0;
		}
		if(vec->y < 0.0 && ((e && (e->getMaterial() && e->getMaterial()->isCollidable())) || (e2 && (e2->getMaterial() && e2->getMaterial()->isCollidable())))){
			player->ResetForces(false, true, false);
			player->getPhysicState()->momentum.y = 0.0;
			speed = 1.0;
		}
		if(vec->y > 0.0 && ((w && (w->getMaterial() && w->getMaterial()->isCollidable())) || (w2 && (w2->getMaterial() && w2->getMaterial()->isCollidable())))){
			player->ResetForces(false, true, false);
			player->getPhysicState()->momentum.y = 0.0;
			speed = 1.0;
		}
		/*
		if(!top || (top->getMaterial() && top->getMaterial()->isCollidable())){
			if(vec->z > 0.0){
				player->ResetForces(false, false, true);
				player->getPhysicState()->momentum.z = 0.0;
			}
			if(jump_force > 0.0){
				jump_force = 0.0;
			}
		}
		*/
	}
	
	player->getLocation().setPitch(client->cam->RotV);
	player->getLocation().setYaw(client->cam->RotH);
	player->ApplyForce(0.0, 0.0, jump_force);
	player->Update(t, dt);
	player->ResetForces();
	player->setMoved(false);

	jump_force = std::max(0.0, jump_force - 5000.0);

	client->cam->x = player->getLocation().getX();
	client->cam->y = player->getLocation().getY();
	client->cam->z = player->getLocation().getZ() + player->getStance();
	geCameraFromAngles(client->cam, client->cam->RotH_rad, client->cam->RotV_rad);
	if(player->isOnGround() && moved){
		client->cam->x += 0.01 * cosf(t * 10.0 / speed);
		client->cam->y += 0.01 * sinf(0.5 + t * 10.0 / speed);
		client->cam->z += 0.06 * sinf(t * 10.0 / speed);
	}

	PickingBlock();
	
	dt = ((float)geGetTick()) / 1000.0 - t;
	t += dt;
	ticks = geWaitTick(1000/60, ticks); // 60 FPS
}

void ThreadCtrl::PickingBlock(){
	int camX = (int)(int)(client->cam->x<0?(client->cam->x-1):client->cam->x);
	int camY = (int)(int)(client->cam->y<0?(client->cam->y-1):client->cam->y);
	int camZ = (int)(int)(client->cam->z<0?(client->cam->z-1):client->cam->z);
	float cam_dirX = ((client->cam->cX<0?(client->cam->cX-1):client->cam->cX) - camX);
	float cam_dirY = ((client->cam->cY<0?(client->cam->cY-1):client->cam->cY) - camY);
	float cam_dirZ = ((client->cam->cZ<0?(client->cam->cZ-1):client->cam->cZ) - camZ);

	int d = 7;
	int zstart = camZ - (cam_dirZ<0 ? d : 1);
	int ystart = camY - (cam_dirY<0 ? d : 1);
	int xstart = camX - (cam_dirX<0 ? d : 1);
	int zend = camZ + 1 + (cam_dirZ>0 ? d : 1);
	int yend = camY + (cam_dirY>0 ? d : 1);
	int xend = camX + (cam_dirX>0 ? d : 1);
	int x, y, z;
	int bx=0, by=0, bz=0;
	
	Block* pp = NULL;
	Chunk* pchunk = NULL;
	float ppdist = 1000.0;
	float ppt = 0.0;
	for(z = zstart; z <= zend; z++){
		if(z < 0 || z >= 256){
			continue;
		}
		for(y = ystart; y <= yend; y++){
			for(x = xstart; x <= xend; x++){
				Chunk* chunk = NULL;
				Block* block = client->getWorld()->FindBlock(x, y, z, &chunk);
				if(block && block->getId() != 0 && (!block->getMaterial() || block->getMaterial()->isClickable()) && chunk){
					bool intersect = false;
					ge_Vector3f box = { (float)x, (float)y, (float)z };
					ge_Vector3f min = { (float)x, (float)y, (float)z };
					ge_Vector3f max = { min.x+1.0f, min.y+1.0f, min.z+1.0f };
					ge_Vector3f rorig = { client->cam->x, client->cam->y, client->cam->z };
					ge_Vector3f rdir = { client->cam->cX-client->cam->x, client->cam->cY-client->cam->y, client->cam->cZ-client->cam->z };

					float t = 0.0;
					intersect = intersectaab(min, max, rorig, rdir, &t);
						
					if(intersect){
						float dist = geDistance3D(rorig.x, rorig.y, rorig.z, box.x+0.5, box.y+0.5, box.z+0.5);
						if(dist <= 6.0 && dist < ppdist){
							ppt = t;
							ppdist = dist;
							pp = block;
							pchunk = chunk;
							bx = x;
							by = y;
							bz = z;
						}
					}
				}
			}
		}
	}
	client->getThreadRender()->SetPickingBlock(pp, bx, by, bz);
	
	if(pp){
		int face = -1;
		float x, y, z;
		float rtemp = cosf(client->cam->RotV_rad);
		z = client->cam->z + (ppt-0.001)*sinf(client->cam->RotV_rad);
		x = client->cam->x + (ppt-0.001)*rtemp*cosf(client->cam->RotH_rad);
		y = client->cam->y + (ppt-0.001)*rtemp*sinf(client->cam->RotH_rad);

		if(z > bz+1.0){
			face = UP;
		}
		if(z < bz){
			face = DOWN;
		}
		if(x > bx+1.0){
			face = SOUTH;
		}
		if(x < bx){
			face = NORTH;
		}
		if(y > by+1.0){
			face = EAST;
		}
		if(y < by){
			face = WEST;
		}
		if(geKeysToggled(client->keys, GEK_LBUTTON)){
		//if(client->getThreadRender()->mouseClickLeft){
			printf("block : %d (%d)\n", pp->getId(), pp->getData());
			Block* b = pp;
			b->setId(0);
			b->setData(0);
			pchunk->MakeSides();
			client->getThreadRender()->EnqueueChunkUpdate(pchunk);
			if((bx & 0x0000000F) == 0){
				Chunk* chunk2 = client->getWorld()->FindChunk(bx-1, by);
				if(chunk2){
					chunk2->MakeSides();
					client->getThreadRender()->EnqueueChunkUpdate(chunk2);
				}
			}
			if((bx & 0x0000000F) == 15){
				Chunk* chunk2 = client->getWorld()->FindChunk(bx+1, by);
				if(chunk2){
					chunk2->MakeSides();
					client->getThreadRender()->EnqueueChunkUpdate(chunk2);
				}
			}
			if((by & 0x0000000F) == 0){
				Chunk* chunk2 = client->getWorld()->FindChunk(bx, by-1);
				if(chunk2){
					chunk2->MakeSides();
					client->getThreadRender()->EnqueueChunkUpdate(chunk2);
				}
			}
			if((by & 0x0000000F) == 15){
				Chunk* chunk2 = client->getWorld()->FindChunk(bx, by+1);
				if(chunk2){
					chunk2->MakeSides();
					client->getThreadRender()->EnqueueChunkUpdate(chunk2);
				}
			}
			client->getThreadRender()->mouseClickLeft = false;
		}else
		if(client->getThreadRender()->mouseClickRight){
			/*
			Block* b2 = NULL;
			Chunk* chunk2 = NULL;
			if(face == UP){
				b2 = client->getWorld()->FindBlock(bx, by, bz+1, &chunk2);
			}
			if(face == DOWN){
				b2 = client->getWorld()->FindBlock(bx, by, bz-1, &chunk2);
			}
			if(face == SOUTH){
				b2 = client->getWorld()->FindBlock(bx+1, by, bz, &chunk2);
			}
			if(face == NORTH){
				b2 = client->getWorld()->FindBlock(bx-1, by, bz, &chunk2);
			}
			if(face == EAST){
				b2 = client->getWorld()->FindBlock(bx, by+1, bz, &chunk2);
			}
			if(face == WEST){
				b2 = client->getWorld()->FindBlock(bx, by-1, bz, &chunk2);
			}
			if(b2 && chunk2){
				printf("b2: %d (%d)\n", b2->getId(), b2->getData());
				b2->setId(world->player.items[select_id]);
				chunk2->MakeSides();
				client->getThreadRender()->EnqueueChunckUpdate(chunk2);
			}
			*/
			client->getThreadRender()->mouseClickRight = false;
		}
	}
}

bool ThreadCtrl::intersectaab(ge_Vector3f min, ge_Vector3f max, ge_Vector3f rorig, ge_Vector3f rdir, float* t){
	float tmin = (min.x - rorig.x) / rdir.x;
	float tmax = (max.x - rorig.x) / rdir.x;
	if (tmin > tmax){
		swap(&tmin, &tmax);
	}
	float tymin = (min.y - rorig.y) / rdir.y;
	float tymax = (max.y - rorig.y) / rdir.y;
	if (tymin > tymax){
		swap(&tymin, &tymax);
	}
	if ((tmin > tymax) || (tymin > tmax)){
		return false;
	}
	if (tymin > tmin){
		tmin = tymin;
	}
	if (tymax < tmax){
		tmax = tymax;
	}
	float tzmin = (min.z - rorig.z) / rdir.z;
	float tzmax = (max.z - rorig.z) / rdir.z;
	if (tzmin > tzmax){
		swap(&tzmin, &tzmax);
	}
	if ((tmin > tzmax) || (tzmin > tmax)){
		return false;
	}
	if(tzmin > tmin){
		tmin = tzmin;
	}
	if(tzmax < tmax){
		tmax = tzmax;
	}
	if((tmin > 100.0) || (tmax < 0.0)){
		return false;
	}
	*t = tmin;
	return true;
}

void ThreadCtrl::swap(float* a, float* b){
	float t = *a;
	*a = *b;
	*b = t;
}
